#include "persister.h"
#include "log.h"

#include "z_base_api.h"

#include <stdlib.h>
#include <limits.h>
#include <error.h>
#include <string.h>

#define ERR_INFO_LEN 4096
__thread char err_info[ERR_INFO_LEN] = {0};

static int persister_load_str(FILE * fp, unsigned long long * seq_no); 
static int persister_save_str(FILE * fp, unsigned long long seq_no);

static int persister_load_bin(FILE * fp, unsigned long long * seq_no); 
static int persister_save_bin(FILE * fp, unsigned long long seq_no);

#define MAX_PATH_LEN 4096
struct persister_s {
    char path_[MAX_PATH_LEN];

    int (*load_)(FILE * fp, unsigned long long * seq_no);
    int (*save_)(FILE * fp, unsigned long long seq_no);
};

void persister_destroy(persister_t * persister) {
    if (persister) {
        free(persister);
    }
}

persister_t * persister_create(const char * path) {
    return persister_create_with_mode(path, FILE_MODE_STR);
}

static inline int set_persist_file_mode(persister_t * persister, int mode) {
    INFO_PRINT("Create persister with mode[%d]:[0 - string, 1 - bin]", mode);
    switch (mode) {
        case FILE_MODE_STR: 
            persister->load_ = persister_load_str;
            persister->save_ = persister_save_str;
            break;
        case FILE_MODE_BIN: 
            persister->load_ = persister_load_bin;
            persister->save_ = persister_save_bin;
            break;
        default:
            ERROR_PRINT("Unrecognized mode[%d]", mode);
            return -1;
    }
    return 0;
}

persister_t * persister_create_with_mode(const char * path, int mode) {
    if (!path) {
        ERROR_PRINT("The file path is NULL");
        return NULL;
    }

    persister_t * persister = (persister_t *)calloc(1, sizeof(persister_t));
    if (!persister) {
        ERROR_PRINT("Persister calloc failed");
        return NULL;
    }

    if (strlen(path) > (MAX_PATH_LEN - 1)) {
        ERROR_PRINT("File path[%s] is too long, the max len supported is %d", path, MAX_PATH_LEN - 1);
        goto ERROR;
    }
    snprintf(persister->path_, MAX_PATH_LEN, "%s", path);

    if (0 != set_persist_file_mode(persister, mode)) {
        goto ERROR;
    }

    return persister;

ERROR:
    persister_destroy(persister);
    return NULL;
}

static int persist_file_close(FILE * fp) {
    if (!fp) {
        ERROR_PRINT("Input file ptr is NULL");
        return -1;
    }
        
    if (0 != fclose(fp)) {
        strerror_r(errno, err_info, ERR_INFO_LEN);
        ERROR_PRINT("File close failed, errno: %d, err msg: %s", errno, err_info);
        return -1;
    }

    return 0;
}

static FILE * persist_file_open(const char * path) {
    if (!path) {
        ERROR_PRINT("Input arg path is NULL");
        return NULL;
    }

    FILE * fp = NULL;
    if (0 == access(path, F_OK)) {
        fp = fopen(path, "r+b");
        if (!fp) {
            strerror_r(errno, err_info, ERR_INFO_LEN);
            FATAL_PRINT("File[%s] open failed, errno: %d, err msg: %s", path, errno, err_info);
            return NULL;
        }
    }
    else {
        INFO_PRINT("File[%s] not exist, need create", path);
        fp = fopen(path, "w+b");
        if (!fp) {
            strerror_r(errno, err_info, ERR_INFO_LEN);
            FATAL_PRINT("Create persist file[%s] failed, errno: %d, err msg: %s", path, errno, err_info);
            return NULL;
        }
    }
    return fp;
}

int persister_load(persister_t * persister, unsigned long long * seq_no) {
    if ((!persister) || (!(persister->path_)) || (!(persister->load_) || (!seq_no))) {
        ERROR_PRINT("Input persister is NULL or load func ptr is NULL or seq_no ptr is NULl");
        return -1;
    }

    FILE * fp = persist_file_open(persister->path_);
    if (!fp) {
        ERROR_PRINT("Open file[%s] failed", persister->path_);
        return -1;
    }

    int ret = persister->load_(fp, seq_no);

    persist_file_close(fp);

    return ret;
}

int persister_save(persister_t * persister, unsigned long long seq_no) {
    if ((!persister) || (!(persister->path_)) || (!(persister->save_))) {
        ERROR_PRINT("Input persister is NULL or save func ptr is NULL");
        return -1;
    }

    FILE * fp = persist_file_open(persister->path_);
    if (!fp) {
        ERROR_PRINT("Open file[%s] failed", persister->path_);
        return -1;
    }

    int ret = persister->save_(fp, seq_no);

    persist_file_close(fp);

    return ret;
}

static inline int trans_str_2_seq_no(char * seq_no_str, unsigned long long * seq_no) {
    char *endptr = NULL;
    errno = 0;
    *seq_no = strtoull(seq_no_str, &endptr, 10);
    if (0 != errno) {
        if (errno == ERANGE) {
            ERROR_PRINT("The num[%s] read from the file out of the range of unsigned long long", seq_no_str);
        }
        else {
            strerror_r(errno, err_info, ERR_INFO_LEN);
            ERROR_PRINT("Read file failed, errno: %d, err msg: %s", errno, err_info);
        }
        return -1;
    }
    
    if (endptr == seq_no_str) {
        ERROR_PRINT("No seq no was found");
        return -1;
    }
    
    if (*endptr != '\0') {
        ERROR_PRINT("Further characters after number:%s", endptr);
        return -1;
    }

    return 0;
}

static int persister_load_str(FILE * fp, unsigned long long * seq_no) {
    if (!fp) {
        ERROR_PRINT("Invalid FILE ptr NULL");
        return -1;
    }

    long len = 0;
    if ((-1 == fseek(fp, 0, SEEK_END)) || (-1 == (len = ftell(fp)))) {
        strerror_r(errno, err_info, ERR_INFO_LEN);
        ERROR_PRINT("Get file length failed, errno: %d, err msg: %s", errno, err_info);
        return -1;
    }

    int ret = 0;
    if (len > 0) {
        char * seq_no_str = (char *)calloc(1, len + 1);
        if (!seq_no_str) {
            ERROR_PRINT("Calloc seq no str failed");
            return -1;
        }

        fseek(fp, 0, SEEK_SET);
        if (fread(seq_no_str, len, 1, fp) != 1) {
            strerror_r(errno, err_info, ERR_INFO_LEN);
            ERROR_PRINT("Read file length[%l] failed, errno: %d, err msg: %s", len, errno, err_info);
            ret = -1;
        }
        else {
            if (0 != trans_str_2_seq_no(seq_no_str, seq_no)) {
                ERROR_PRINT("Seq no read from file trans failed");
                ret = -1;
            }
        }

        free(seq_no_str);
    }
    else {
        *seq_no = 0;     
    }

    return ret;
}

#define LONGLONG_MAX_LEN 50
static int persister_save_str(FILE * fp, unsigned long long seq_no) {
    if (!fp) {
        ERROR_PRINT("Invalid FILE ptr NULL");
        return -1;
    }

    char seq_no_str[LONGLONG_MAX_LEN] = {0};
    int seq_no_str_len = snprintf(seq_no_str, LONGLONG_MAX_LEN, "%llu", seq_no);

    int fd = fileno(fp); 
    if (-1 == fd) {
        ERROR_PRINT("Open is not a valid stream");
        return -1;
    }

    if (0 != ftruncate(fd, 0)) {
        strerror_r(errno, err_info, ERR_INFO_LEN);
        ERROR_PRINT("Clear file[fd:%d] failed, errno: %d, err msg: %s", fd, errno, err_info);
        return -1;
    }

    fseek(fp, 0, SEEK_SET);
    if (fwrite(seq_no_str, seq_no_str_len, 1, fp) != 1) {
        strerror_r(errno, err_info, ERR_INFO_LEN);
        ERROR_PRINT("Write file failed, content: %s, len: %d. errno: %d, err msg: %s", seq_no_str, seq_no_str_len, errno, err_info);
        return -1;
    }

    return 0;
}

static int persister_load_bin(FILE * fp, unsigned long long * seq_no) {
    if (!fp) {
        ERROR_PRINT("Invalid FILE ptr NULL");
        return -1;
    }

    long len = 0;
    if ((-1 == fseek(fp, 0, SEEK_END)) || (-1 == (len = ftell(fp)))) {
        strerror_r(errno, err_info, ERR_INFO_LEN);
        ERROR_PRINT("Get file length failed, errno: %d, err msg: %s", errno, err_info);
        return -1;
    }

    if (len > 0) {
        fseek(fp, 0, SEEK_SET);
        if (fread(seq_no, sizeof(unsigned long long), 1, fp) != 1) {
            strerror_r(errno, err_info, ERR_INFO_LEN);
            ERROR_PRINT("Read file failed, errno: %d, err msg: %s", errno, err_info);
            return -1;
        }
    }
    else {
        *seq_no = 0;
    }

    return 0;
}

static int persister_save_bin(FILE * fp, unsigned long long seq_no) {
    if (!fp) {
        ERROR_PRINT("Invalid FILE ptr NULL");
        return -1;
    }

    fseek(fp, 0, SEEK_SET);
    if (fwrite(&seq_no, sizeof(seq_no), 1, fp) != 1) {
        strerror_r(errno, err_info, ERR_INFO_LEN);
        ERROR_PRINT("Write file failed, errno: %d, err msg: %s", errno, err_info);
        return -1;
    }

    return 0; 
}
